package com.ternnetwork.core.notice.service.impl;

import com.ternnetwork.commons.model.http.BaseResponse;
import com.ternnetwork.commons.utils.Md5Utils;
import com.ternnetwork.core.accesscontroll.entity.User;
import com.ternnetwork.core.accesscontroll.service.UserService;
import com.ternnetwork.core.notice.dto.NoticeDto;
import com.ternnetwork.core.notice.dto.NoticeQueryDto;
import com.ternnetwork.core.notice.entity.Notice;
import com.ternnetwork.core.notice.entity.NoticeCategory;
import com.ternnetwork.core.notice.entity.NoticeReadLog;
import com.ternnetwork.core.notice.entity.NoticeReadLogId;
import com.ternnetwork.core.notice.enums.messages.NoticeMessages;
import com.ternnetwork.core.notice.repository.NoticeCategoryRepository;
import com.ternnetwork.core.notice.repository.NoticeReadLogRepository;
import com.ternnetwork.core.notice.repository.NoticeRepository;
import com.ternnetwork.core.notice.service.NoticeService;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.repository.support.PageableExecutionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;

@Service
public class NoticeServiceImpl implements NoticeService {


    private final NoticeRepository noticeRepository;

    private final NoticeCategoryRepository noticeCategoryRepository;

    private final UserService userService;


    private final EntityManager entityManager;


    private final NoticeReadLogRepository noticeReadLogRepository;


    public NoticeServiceImpl(NoticeRepository noticeRepository, NoticeCategoryRepository noticeCategoryRepository, UserService userService, EntityManager entityManager, NoticeReadLogRepository noticeReadLogRepository) {
        this.noticeRepository = noticeRepository;
        this.noticeCategoryRepository = noticeCategoryRepository;
        this.userService = userService;
        this.entityManager = entityManager;
        this.noticeReadLogRepository = noticeReadLogRepository;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse add(NoticeDto dto, String username){

        BaseResponse  baseResponse=checkProperties(dto);

        if(baseResponse.getStatus().intValue()==0) {

            return baseResponse;
        }

        Notice notice=new Notice();

        BeanUtils.copyProperties(dto,notice);

        if(dto.getCategoryId()!=null){

            NoticeCategory noteCategory= noticeCategoryRepository.getOne(dto.getCategoryId());

            notice.setCategory(noteCategory);
        }
        User loginUser=userService.findByName(username);

        notice.setCreatedById(loginUser.getId());

        noticeRepository.saveAndFlush(notice);

        baseResponse.setSccucess(notice.getId());

        return baseResponse;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse update(NoticeDto dto, String username){

        BaseResponse  baseResponse=checkProperties(dto);

        if(baseResponse.getStatus().intValue()==0) {

            return baseResponse;
        }

        Optional<Notice> optional= noticeRepository.findById(dto.getId());

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }
        Notice notice=optional.get();

        if(notice.getReaderCount()!=0L){

            baseResponse.setFailed(NoticeMessages.FAILED_MESSAGE_HAS_READER.name());

            return baseResponse;
        }

        User loginUser=userService.findByName(username);

        if(dto.getCategoryId()!=null){

            NoticeCategory noteCategory= noticeCategoryRepository.getOne(dto.getCategoryId());

            notice.setCategory(noteCategory);
        }
        BeanUtils.copyProperties(dto,notice);

        notice.setUpdatedById(loginUser.getId());

        notice.setUpdateTime(new Date());

        notice.setUpdatedById(loginUser.getId());

        noticeRepository.saveAndFlush(notice);

        baseResponse.setSccucess();

        return baseResponse;
    }



    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse deleteById(Long id,String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Notice> optional= noticeRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }


        Notice notice=optional.get();

        if(notice.getReaderCount()!=0L){

            baseResponse.setFailed(NoticeMessages.FAILED_MESSAGE_HAS_READER.name());

            return baseResponse;
        }

        noticeRepository.delete(notice);

        baseResponse.setSccucess();

        return baseResponse;
    }

    @Override
    public BaseResponse findByPage(Pageable pageable, NoticeQueryDto dto, String username) {

        BaseResponse  baseResponse=new BaseResponse();

        User loginUser=userService.findByName(username);

        dto.setUserId(loginUser.getId());

        CriteriaBuilder criteriaBuilder =this.entityManager.getCriteriaBuilder();

        CriteriaQuery<Notice> criteriaQuery=criteriaBuilder.createQuery(Notice.class);

        Root<Notice> root=criteriaQuery.from(Notice.class);

        //tell it what field we need here
        criteriaQuery.multiselect(root.get("id"),
                        root.get("title"),
                        root.get("createTime"),
                        root.get("updateTime"),
                        root.get("published"),
                        root.get("publishedTime"),
                        root.get("publishedCode"),
                        root.get("readerCount"))
                .where(getPredicate(dto,root,criteriaQuery,criteriaBuilder))

                .orderBy(criteriaBuilder.desc(root.get("publishedTime")));

        List<Notice> noteList=this.entityManager
                .createQuery(criteriaQuery)
                .setFirstResult((int)pageable.getOffset())
                .setMaxResults(pageable.getPageSize()).getResultList();

        Page<Notice> page= PageableExecutionUtils.getPage(noteList,pageable,()->this.noticeRepository.count(getSpecification(dto)));

        baseResponse.setSccucess(page);

        return baseResponse;
    }

    private Specification getSpecification(NoticeQueryDto dto){

        return (root, criteriaQuery, criteriaBuilder) -> {

            return criteriaQuery.where(getPredicate(dto,root,criteriaQuery,criteriaBuilder)).getRestriction();
        };

    }

    private Predicate[]  getPredicate(NoticeQueryDto dto, Root<Notice> root,CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder){

        List<Predicate> predicates = new ArrayList<>();

        if(!StringUtils.isEmpty(dto.getTitle())){

            predicates.add(criteriaBuilder.like(root.get("title").as(String.class),"%".concat(dto.getTitle()).concat("%")));
        }

        if(dto.getCategoryId()!=null){

            predicates.add(criteriaBuilder.equal(root.get("category").get("id").as(Long.class),dto.getCategoryId()));
        }

        if(dto.getPublished()!=null){

            predicates.add(criteriaBuilder.equal(root.get("published").as(Boolean.class),dto.getPublished()));

        }
        //未阅读
        if(dto.getRead()!=null){
            //select notice from Notice t where t.id not in (select  l.notice.id from NoticeReadLog l where l.user.id=:userId)
            Subquery<Long> subQuery = criteriaQuery.subquery(Long.class);
            Root<NoticeReadLog> subRootFrom = subQuery.from(NoticeReadLog.class);
            List<Predicate> subPredicatesList = new ArrayList<>();
            Predicate[] subPredicatesArray = new Predicate[subPredicatesList.size()];
            subPredicatesList.add(criteriaBuilder.equal(subRootFrom.get("user").get("id").as(Long.class),dto.getUserId()));
            subQuery.select(subRootFrom.get("notice").get("id").as(Long.class))
                    .where(subPredicatesList.toArray(subPredicatesArray));
            if(dto.getRead()==Boolean.FALSE) {

                predicates.add(criteriaBuilder.not(criteriaBuilder.in(root.get("id").as(Long.class)).value(subQuery)));
            }else{
                predicates.add(criteriaBuilder.in(root.get("id").as(Long.class)).value(subQuery));
            }

        }

        Predicate[] predicateArray = new Predicate[predicates.size()];

        return predicates.toArray(predicateArray);
    }


    private BaseResponse checkProperties (NoticeDto t) {

        BaseResponse  baseResponse=new BaseResponse();

        if(StringUtils.isEmpty(t.getTitle())) {

            baseResponse.setFailed(NoticeMessages.FAILED_MESSAGE_TITLE_CAN_NOT_BE_EMPTY.getName());

            return baseResponse;
        }

        baseResponse.setSccucess();

        return baseResponse;
    }


    public BaseResponse getOne(Long id,String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Notice> optional= noticeRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Notice notice=optional.get();


        baseResponse.setSccucess(notice);

        return baseResponse;
    }

    public BaseResponse publish(Long id, String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Notice> optional= noticeRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Notice notice=optional.get();

        if(notice.getPublished()==Boolean.TRUE){

            baseResponse.setFailed(NoticeMessages.FAILED_MESSAGE_HAS_BEEN_PUBLISHED.name());

            return baseResponse;
        }

        notice.setPublished(Boolean.TRUE);

        notice.setPublishedTime(new Date());

        String publishedCode=getPublishedCode(notice);

        notice.setPublishedCode(publishedCode);

        noticeRepository.saveAndFlush(notice);

        baseResponse.setSccucess(publishedCode);

        return baseResponse;
    }

    public BaseResponse deletePublish(Long id, String username){

        BaseResponse  baseResponse=new BaseResponse();

        Optional<Notice> optional= noticeRepository.findById(id);

        if(optional.equals(Optional.empty())){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Notice notice=optional.get();

        if(notice.getReaderCount()!=0L){

            baseResponse.setFailed(NoticeMessages.FAILED_MESSAGE_HAS_READER.name());

            return baseResponse;
        }

        notice.setPublished(Boolean.FALSE);

        notice.setPublishedCode(null);

        notice.setPublishedTime(null);

        noticeRepository.saveAndFlush(notice);

        baseResponse.setSccucess();

        return baseResponse;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
    public BaseResponse getPublishedOne(String publishedCode,String username){

        BaseResponse  baseResponse=new BaseResponse();

        List<Notice> noteList= noticeRepository.findAllByPublishedCode(publishedCode);

        if(noteList.size()==0){

            baseResponse.setFailedForObjectNotExist();

            return baseResponse;
        }

        Notice notice=noteList.get(0);

        User loginUser=userService.findByName(username);

        NoticeReadLogId noticeReadLogId=new NoticeReadLogId();

        noticeReadLogId.setNoticeId(notice.getId());
        noticeReadLogId.setUserId(loginUser.getId());


        Optional<NoticeReadLog> optional=noticeReadLogRepository.findById(noticeReadLogId);

        if(optional.equals(Optional.empty())){

            NoticeReadLog noticeReadLog=new NoticeReadLog();

            noticeReadLog.setNotice(notice);

            noticeReadLog.setUser(loginUser);

            noticeReadLog.setId(noticeReadLogId);

            notice.setReaderCount(notice.getReaderCount()+1);

            noticeRepository.save(notice);

            noticeReadLogRepository.save(noticeReadLog);
        }


        baseResponse.setSccucess(notice);

        return baseResponse;
    }


    private String getPublishedCode(Notice note){
        return Md5Utils.MD5(String.valueOf(note.getId()).concat("-").concat(String.valueOf(note.getCreatedById())));
    }


    public BaseResponse countAll(){

        BaseResponse  baseResponse=new BaseResponse();

        long totalCount= noticeRepository.countAll();

        baseResponse.setSccucess(totalCount);

        return baseResponse;
    }




}
